SpringCloud 入门笔记(七)Config配置中心

概述

在微服务架构中,一个系统划分为多个服务,每个服务有各自的配置,Spring Cloud Config提供了集中管理服务配置的功能,并且可以在服务运行时动态调整配置,而不需要重启服务。

Spring Cloud Config包括Config Server和Config Client两部分。

Config Server是一个可横向扩展、集中式的配置服务器,默认使用Git存储配置内容,本篇中也将使用Git存储各服务的配置。

Config Client即为各个微服务。在服务启动时,会向Config Server请求所需的配置属性,并缓存在本地使用。

构建Config Server

创建Github配置仓库

在github创建一个配置仓库 spring-cloud-demo-config,并创建config-client-dev.yml,内容如下:

config-client-dev.yml:

1
routes: "{'user-ms': '/u/**', 'role-ms': '/r/**'}"

可以看到在这个配置文件中我们写入了一些服务网关的路由规则,当然也可以添加一些其他的配置,这里只是作为一个例子。

创建config-server项目

config-server作为配置中心,本身也是一个服务,因此也可以注册到服务注册中心中。在创建config-server项目时,勾选Eureka Discovery和Config Server两个依赖,创建成功的项目中将包括如下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

编写配置文件,添加git仓库的地址和服务注册配置,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 8805
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/GreedyStar/spring-cloud-demo-config.git
label: master
eureka:
client:
service-url:
defaultZone: http://admin:admin123@127.0.0.1:8000/eureka/

如果是私有项目的话,还需要在git节点下配置username和password。

修改config-server启动类,添加@EnableConfigServer和@EnableEurekaClient注解,如下:

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}

这样,config-server配置中心就构建完成了。

测试访问配置数据

我们可以在config-server通过http请求访问git仓库的配置数据,访问规则如下:

  • /{name}/{profile}[/{label}]
  • /{name}-{profile}.yml
  • /{label}/{name}-{profile}.yml
  • /{name}-{profile}.properties
  • /{label}/{name}-{profile}.properties

name:表示服务名称,如user-ms、role-ms

profile:表示config-client配置的profile,如dev、test、prod等

label:表示git仓库的分支

例如我们可以通过访问 http://localhost:8805/config-client/dev/master 来获取github仓库中config-client-dev.yml的配置内容

构建Config Client

添加依赖

创建一个名为config-client的工程来进行测试,由于我们在配置中心配置的是一些路由规则,这里的config-client就以zuul服务网关为例进行测试。

首先,为config-client工程添加如下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

配置bootstrap.yml

bootstrap.yml用于配置config数据的拉取,在resources目录下新建bootstrap.yml,之所以使用bootstrap.yml而不是application.yml,是因为我们的配置数据大多是用来配置IOC容器的,而bootstrap.yml会在IOC容器启动前加载,以此可以实现配置数据的远端加载,bootstrap.yml内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
application:
name: config-client
cloud:
config:
profile: dev
label: master
# uri: http://localhost:8010/ # 在不将config-server注册到eureka-server时需要用到
discovery:
enabled: true
service-id: config-server
eureka:
client:
service-url:
defaultZone: http://admin:admin123@127.0.0.1:8000/eureka/

这里有一点需要注意,前面的内容中,我们将config-server注册到了eureka-server,因此我们可以从eureka-server来发现config-server服务,然后拉取配置数据。如果没有将config-server注册到eureka-server,则可以直接在uri节点指定config-server的地址。

配置application.yml

1
2
server:
port: 8806

可以看到,我们没有为config-client配置路由规则。

创建配置类

首先,创建一个RouteProperty类,用于接收从config-server拉取的配置数据,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class RouteProperty {
@Value("#{${routes}}")
private Map<String, String> routes = new HashMap<>();

public Map<String, String> getRoutes() {
return routes;
}

public void setRoutes(Map<String, String> routes) {
this.routes = routes;
}
}

@Value注解中的Key对应Github配置仓库中配置文件中的key。

然后,创建一个自定义的RouteLocator,用于手动注册路由规则,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CustomRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
private RouteProperty routeProperty;

public CustomRouteLocator(String servletPath, ZuulProperties properties, RouteProperty routeProperty) {
super(servletPath, properties);
this.routeProperty = routeProperty;
}

@Override
protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap();
routesMap.putAll(super.locateRoutes());
for (Map.Entry<String, String> entry : routeProperty.getRoutes().entrySet()) {
routesMap.put(entry.getValue(), new ZuulProperties.ZuulRoute(entry.getKey(), entry.getValue(), entry.getKey(), null, true
, true, null));
}
return routesMap;
}

@Override
public void refresh() {
doRefresh();
}
}

最后,创建Zuul路由规则的配置类,将我们自定义的RouteLocator注入IOC容器中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class ZuulConfig {
@Autowired
private ServerProperties server;
@Autowired
private ZuulProperties zuulProperties;
@Autowired
private RouteProperty routeProperty;

@Bean
public RouteLocator routeLocator() {
CustomRouteLocator routeLocator = new CustomRouteLocator(server.getServlet().getContextPath(),
zuulProperties, routeProperty);
return routeLocator;
}

}

这样,我们就实现了从config-server拉取Zuul的路由规则,并应用于config-client服务中。

测试配置数据拉取

按顺序启动 eureka-server、config-server、config-client、user-ms、role-ms

可以看到user-ms在启动时会向config-server拉取配置信息

而且我们可以通过在配置中心中配置的路由规则来访问user-ms和role-ms

动态配置更新

在一些情况下,我们可能需要对线上的服务进行配置修改,但我们在Git仓库修改配置信息后,config-server可以读取到新的配置,而config-client(各微服务)由于在本地缓存了配置而无法动态更新配置信息,我们可以借助Actuator实现在不重启服务的情况下更新服务的配置,下面对config-client进行一些改造,让它支持动态配置更新。

配置Actuator

Actuator可以帮助我们监控和管理Spring Boot应用,并通过一些HTTP端点来进行健康检查、统计等操作。

为user-ms添加如下依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

修改application.yml,添加如下配置:

1
2
3
4
5
management:
endpoints:
web:
exposure:
include: refresh,health

上面的配置表示向外部暴露/actuator/refresh和/actuator/health接口,我们可以通过/actuator/refresh来通知client更新配置。

添加@RefreshScope注解

@RefreshScope注解是实现动态配置更新的重要注解。

当我们调用/actuator/refresh时,springboot会用新配置创建一个新的IOC容器,然后与原IOC容器的配置进行比较,找出那些修改了的配置,然后使用新配置重新创建由@RefreshScope注解的Bean。

修改RouteProperty类和ZuulConfig类,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RefreshScope
@Component
public class RouteProperty {
@Value("#{${routes}}")
private Map<String, String> routes = new HashMap<>();

public Map<String, String> getRoutes() {
return routes;
}

public void setRoutes(Map<String, String> routes) {
this.routes = routes;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class ZuulConfig {
@Autowired
private ServerProperties server;
@Autowired
private ZuulProperties zuulProperties;
@Autowired
private RouteProperty routeProperty;

@RefreshScope
@Bean
public RouteLocator routeLocator() {
CustomRouteLocator routeLocator = new CustomRouteLocator(server.getServlet().getContextPath(),
zuulProperties, routeProperty);
return routeLocator;
}

}

这里有一点需要注意,@RefreshScope注解不应与@Configuration注解同时使用,官方给出的说明是同时使用会出现意想不到的情况,@RefreshScope应当标注在那些需要重新创建的Bean上。

OK,到这里,动态配置更新就完成了,下面简单测试一下。

测试配置更新

首先,按照配置中心配置的路由规则来发送一次请求,如下所示:

然后,将github中的config-client-dev.yml内容修改为:

1
routes: "{'user-ms': '/user/**', 'role-ms': '/role/**'}"

调用refresh端点通知config-client刷新配置,如下所示:

再然后,我们依然按照原来的方式发送请求,就出现404错误,如下:

最后,我们按照修改后的规则来进行调用,如下所示:

可以看到,config-client的路由规则已经发生了改变。

通常,我们会在配置修改后自动通知config-client刷新配置,这可以借助消息队列来实现,我们在这里就先不去实验了。

源码地址:https://github.com/GreedyStar/spring-cloud-demo

最后的最后,安利一下自己写的一个Java代码生成工具,能够方便的生成Spring、SpringMVC、Mybatis架构下的Java代码,希望能对大家有所帮助,地址:Java代码生成器:Generator